package org.parngon.maze;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Color;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

import java.util.LinkedList;

public class MazePanel extends JPanel
{
  static int MIN_INTERVAL_PER_TILE = 250;
  static int THICKNESS = 1;
  static Color cSelection = new Color(0, 0, 255, 50);
  static Stroke gridStroke = new BasicStroke(0.4f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f);
  static Stroke thickStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f);

  Maze theMaze = null;
  int mcol, mrow;
  Tile start = null;
  Tile end = null;
  Point lastJoint = null;
  Point currentJoint = null;
  Point startJoint = null;
  Point endJoint = null;

  long robotStartMoving = -1;
  Point robotNodeLocation = null;
  Point robotRealLocation = null;

  BufferedImage mazeImage = null;
  boolean renderImage = true;

  LinkedList <Point>solutionPoints = null;

  Tile topLeft = null;
  Tile bottomRight = null;
  Dimension oldSize = null;

  double rotateAngle = 0;

  public MazePanel()
  {
    renderImage = true;
    start = end = null;
    mcol = mrow = 0;

    setOpaque(true);
    Thread dynamics = new Thread() {
      public void run()
      {
        while ( true ) {
          if ( MazePanel.this.isDisplayable() && MazePanel.this.solutionPoints != null && MazePanel.this.solutionPoints.size() > 1 ) {
            if ( MazePanel.this.robotStartMoving == -1 ) {
              MazePanel.this.robotStartMoving = System.currentTimeMillis();
            }
            if ( MazePanel.this.robotStartMoving != -1 ) {
              MazePanel.this.updateRobotLocation();
              MazePanel.this.repaint();
            }
          }
          try {
            sleep(10);
          } catch ( InterruptedException ie ) {
          }
        }
      }
    };
    dynamics.start();
/*
    Thread dynamics = new Thread() {
      public void run()
      {
        while ( true ) {
          if ( MazePanel.this.isDisplayable() ) {
            MazePanel.this.rotateAngle += Math.PI / 180 / 100;
            MazePanel.this.repaint();
          }
          try {
            sleep(10);
          } catch ( InterruptedException ie ) {
          }
        }
      }
    };
    dynamics.start();
*/
  }

  public void initMaze(int col, int row, double bw, double fde)
  {
    renderImage = true;

    if ( theMaze != null ) {
      Dimension osz = theMaze.getSize();
      if ( osz.width != col || osz.height != row ) {
        theMaze = new Maze(col, row);
      }
    } else {
      theMaze = new Maze(col, row);
    }

    long t0 = System.currentTimeMillis();
    theMaze.generate();
    long t1 = System.currentTimeMillis();

    System.out.println("Maze construction: " + (t1 - t0) / 1000.0);
    theMaze.breakWalls(bw);
    long t2 = System.currentTimeMillis();
    System.out.println("Break wall: " + (t2 - t1) / 1000.0);
    theMaze.fillDeadends(fde);
    long t3 = System.currentTimeMillis();
    System.out.println("Remove deadends: " + (t3 - t2) / 1000.0);

/*
    DeadendPair dp = maze.getFurtherestDeadendPair();
    long t4 = System.currentTimeMillis();
    System.out.println("Sort for farest deadends: " + (t4 - t3) / 1000.0);

    if ( dp != null ) {
      solution = as.findPath(dp.getStart(), dp.getEnd(), theMaze);
      long t5 = System.currentTimeMillis();
      System.out.println("A* solving: " + (t5 - t4) / 1000.0);
    } else {
      solution = null;
    }
*/
    start = end = null;
    //solution = null;

    repaint();
  }

  public void initMaze(int col, int row)
  {
    initMaze(col, row, 0.3, 0.0);
  }

  public void setStartTile(Tile s)
  {
    start = s;
    resetRobot();
  }

  public void setEndTile(Tile e)
  {
    end = e;
  }

  public void setSolutionPoints(LinkedList <Point>sps)
  {
    solutionPoints = sps;
  }

  public boolean renderMazeImage(Maze model)
  {
    long t0 = System.currentTimeMillis();

    rotateAngle = 0;

    int stepX = getWidth() / model.getWidth();
    int stepY = getHeight() / model.getHeight();
    int step = Math.min(stepX, stepY);
    int dx = stepX - 1;
    int dy = stepY - 1;
    int dd = step - 1;

//    int realWidth = stepX * model.getWidth();
//    int realHeight = stepY * model.getHeight();
    int realWidth = step * model.getWidth();
    int realHeight = step * model.getHeight();

    mazeImage = new BufferedImage(realWidth, realHeight, BufferedImage.TYPE_4BYTE_ABGR_PRE);

    Graphics2D lg2d = mazeImage.createGraphics();

    //lg2d.setColor(Color.black);
    lg2d.setColor(Color.green.darker());
    //lg2d.fillRect(0, 0, getWidth(), getHeight());
    lg2d.fillRect(0, 0, realWidth, realHeight);
    //lg2d.clearRect(0, 0, realWidth, realHeight);

    lg2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    //lg2d.setColor(Color.gray.brighter());
    lg2d.setColor(new Color(230, 230, 230, 255));
//    for ( int i = 0; i < realHeight; i += stepY ) lg2d.drawLine(0, i, realWidth - 1, i);
//    for ( int i = 0; i < realWidth; i += stepX ) lg2d.drawLine(i, 0, i, realHeight - 1);
    for ( int i = 0; i < realHeight; i += step ) lg2d.drawLine(0, i, realWidth - 1, i);
    for ( int i = 0; i < realWidth; i += step ) lg2d.drawLine(i, 0, i, realHeight - 1);

    Color wallColor = Color.black;
    for ( int i = 0; i < mcol; i++ ) {
      for ( int ii = 0; ii < mrow; ii++ ) {
        Tile t = model.getTile(i, ii);

//        int tx = i * stepX + 1;
//        int ty = ii * stepY + 1;
        int tx = i * step + 1;
        int ty = ii * step + 1;

        lg2d.translate(tx, ty);

        if ( t.isBrick() || t.hasLeftWall() && t.hasRightWall() && t.hasTopWall() && t.hasBottomWall() ) {
          lg2d.setColor(wallColor);
//          lg2d.fillRect(0, 0, dx, dy);
          lg2d.fillRect(0, 0, dd, dd);
        } else {
          Color tileColor = t.isDeadend() ? new Color(0.9f, 0.9f, 0.8f, 1.0f) : Color.white;

          lg2d.setColor(tileColor);
//          lg2d.fillRect(THICKNESS, THICKNESS, dx - THICKNESS * 2, dy - THICKNESS * 2);
          lg2d.fillRect(THICKNESS, THICKNESS, dd - THICKNESS * 2, dd - THICKNESS * 2);

          if ( t.top != null && t.top.hasLeftWall() || t.left != null && t.left.hasTopWall() ) {
            lg2d.setColor(wallColor);
          } else {
            lg2d.setColor(tileColor);
          }
          lg2d.fillRect(0, 0, THICKNESS, THICKNESS);
          if ( t.top != null && t.top.hasRightWall() || t.right != null && t.right.hasTopWall() ) {
            lg2d.setColor(wallColor);
          } else {
            lg2d.setColor(tileColor);
          }
//          lg2d.fillRect(dx - THICKNESS, 0, THICKNESS, THICKNESS);
          lg2d.fillRect(dd - THICKNESS, 0, THICKNESS, THICKNESS);
          if ( t.bottom != null && t.bottom.hasLeftWall() || t.left != null && t.left.hasBottomWall() ) {
            lg2d.setColor(wallColor);
          } else {
            lg2d.setColor(tileColor);
          }
//          lg2d.fillRect(0, dy - THICKNESS, THICKNESS, THICKNESS);
          lg2d.fillRect(0, dd - THICKNESS, THICKNESS, THICKNESS);
          if ( t.bottom != null && t.bottom.hasRightWall() || t.right != null && t.right.hasBottomWall() ) {
            lg2d.setColor(wallColor);
          } else {
            lg2d.setColor(tileColor);
          }
//          lg2d.fillRect(dx - THICKNESS, dy - THICKNESS, THICKNESS, THICKNESS);
          lg2d.fillRect(dd - THICKNESS, dd - THICKNESS, THICKNESS, THICKNESS);

          if ( t.hasTopWall() ) {
            lg2d.setColor(wallColor);
//            lg2d.fillRect(0, 0, dx, THICKNESS);
            lg2d.fillRect(0, 0, dd, THICKNESS);
          } else {
            lg2d.setColor(tileColor);
//            lg2d.fillRect(THICKNESS, 0, dx - THICKNESS * 2, THICKNESS);
            lg2d.fillRect(THICKNESS, 0, dd - THICKNESS * 2, THICKNESS);
          }
          if ( t.hasBottomWall() ) {
            lg2d.setColor(wallColor);
//            lg2d.fillRect(0, dy - THICKNESS, dx, THICKNESS);
            lg2d.fillRect(0, dd - THICKNESS, dd, THICKNESS);
          } else {
            lg2d.setColor(tileColor);
//            lg2d.fillRect(THICKNESS, dy - THICKNESS, dx - THICKNESS * 2, THICKNESS);
            lg2d.fillRect(THICKNESS, dd - THICKNESS, dd - THICKNESS * 2, THICKNESS);
          }
          if ( t.hasLeftWall() ) {
            lg2d.setColor(wallColor);
//            lg2d.fillRect(0, 0, THICKNESS, dy);
            lg2d.fillRect(0, 0, THICKNESS, dd);
          } else {
            lg2d.setColor(tileColor);
//            lg2d.fillRect(0, THICKNESS, THICKNESS, dy - THICKNESS * 2);
            lg2d.fillRect(0, THICKNESS, THICKNESS, dd - THICKNESS * 2);
          }
          if ( t.hasRightWall() ) {
            lg2d.setColor(wallColor);
//            lg2d.fillRect(dx - THICKNESS, 0, THICKNESS, dy);
            lg2d.fillRect(dd - THICKNESS, 0, THICKNESS, dd);
          } else {
            lg2d.setColor(tileColor);
//            lg2d.fillRect(dx - THICKNESS, THICKNESS, THICKNESS, dy - THICKNESS * 2);
            lg2d.fillRect(dd - THICKNESS, THICKNESS, THICKNESS, dd - THICKNESS * 2);
          }
        }
        if ( t.isMarked() ) {
          if ( t.getMark() == 1 ) {
            lg2d.setColor(Color.yellow);
          } else if ( t.getMark() == 2 ) {
            lg2d.setColor(Color.red);
          } else if ( t.getMark() == 3 ) {
            lg2d.setColor(Color.green);
          } else if ( t.getMark() == 4 ) {
            lg2d.setColor(Color.orange);
          } else {
            lg2d.setColor(Color.blue);
          }
//          lg2d.drawOval(THICKNESS * 2, THICKNESS * 2, dx - THICKNESS * 4, dy - THICKNESS * 4);
          lg2d.drawOval(THICKNESS * 2, THICKNESS * 2, dd - THICKNESS * 4, dd - THICKNESS * 4);
          lg2d.setColor(Color.black);
        }
        lg2d.translate(-tx, -ty);
      }
    }

    lg2d.dispose();

    long t1 = System.currentTimeMillis();
    System.out.println("renderMazeImage(): " + (t1 - t0) / 1000.0);
    return true;
  }

  public void setMazeSize(Dimension sz)
  {
    mcol = sz.width;
    mrow = sz.height;
  }

  public synchronized void paintComponent(Graphics g)
  {
    super.paintComponent(g);

    //if ( theMaze == null ) return;

    /*
    int mcol = theMaze.getSize().width;
    int mrow = theMaze.getSize().height;
    System.out.println("paintComponent called");
    */

    if ( mcol == 0 || mrow == 0 ) return;

    int stepX = getWidth() / mcol;
    int stepY = getHeight() / mrow;
    int step = Math.min(stepX, stepY);
//    int realWidth = stepX * mcol;
//    int realHeight = stepY * mrow;
    int realWidth = step * mcol;
    int realHeight = step * mrow;
    int offsetX = (getWidth() - realWidth) / 2;
    int offsetY = (getHeight() - realHeight) / 2;

    // create the 'static' maze image if needed
//    if ( renderImage ) renderMazeImage();
    g.clearRect(0, 0, getWidth(), getHeight());

    g.translate(getWidth() / 2, getHeight() / 2);
    ((Graphics2D)g).rotate(rotateAngle);
    g.translate(-getWidth() / 2, -getHeight() / 2);

    g.translate(offsetX, offsetY);

    if ( mazeImage != null ) g.drawImage(mazeImage, 0, 0, this);

    if ( lastJoint != null ) {
      int ss = (int)(step * 0.2);
      int lx = lastJoint.x * step;
      int ly = lastJoint.y * step;
      g.setColor(new Color(1.0f, 0.0f, 0.0f, 0.5f));
      g.fillPolygon(new int[] { lx - ss, lx, lx + ss + 1, lx }, new int[] { ly, ly - ss, ly, ly + ss + 1 }, 4);
    }
    if ( currentJoint != null ) {
//      int ss = (int)Math.min(stepX * 0.2, stepY * 0.2);
      int ss = (int)(step * 0.2);
//      int lx = currentJoint.x * stepX;
//      int ly = currentJoint.y * stepY;
      int lx = currentJoint.x * step;
      int ly = currentJoint.y * step;
      g.setColor(new Color(0.0f, 0.0f, 1.0f, 0.5f));
      g.fillPolygon(new int[] { lx - ss, lx, lx + ss + 1, lx }, new int[] { ly, ly - ss, ly, ly + ss + 1 }, 4);
    }
    if ( lastJoint != null && currentJoint != null ) {
      int ss = (int)(step * 0.2) - 1;
      int sx = lastJoint.x * step;
      int sy = lastJoint.y * step;
      int ex = currentJoint.x * step;
      int ey = currentJoint.y * step;
      g.setColor(new Color(0.0f, 1.0f, 0.0f, 0.5f));
      ((Graphics2D)g).setStroke(thickStroke);
      g.drawLine(sx, sy, ex, ey);
    }
/*
    if ( startJoint != null && endJoint != null ) {
      int sx = startJoint.x * stepX;
      int sy = startJoint.y * stepY;
      int ex = endJoint.x * stepX;
      int ey = endJoint.y * stepY;
      g.setColor(Color.blue);
      g.fillPolygon(new int[] { sx - 1, sx + 1, ex + 1, ex - 1 }, new int[] { sy - 1, ly - ss, ly, ly + ss }, 4);
    }
*/
    if ( start != null ) {
      Point sp = start.getLocation();
      g.setColor(Color.green);
//      g.fillRect(sp.x * stepX + 1 + THICKNESS * 2, sp.y * stepY + 1 + THICKNESS * 2, stepX - THICKNESS * 4 - 1, stepY - THICKNESS * 4 - 1);
      g.fillRect(sp.x * step + 1 + THICKNESS * 2, sp.y * step + 1 + THICKNESS * 2, step - THICKNESS * 4 - 1, step - THICKNESS * 4 - 1);
    }
    if ( end != null ) {
      Point sp = end.getLocation();
      g.setColor(Color.green);
//      g.fillRect(sp.x * stepX + 1 + THICKNESS * 2, sp.y * stepY + 1 + THICKNESS * 2, stepX - THICKNESS * 4 - 1, stepY - THICKNESS * 4 - 1);
      g.fillRect(sp.x * step + 1 + THICKNESS * 2, sp.y * step + 1 + THICKNESS * 2, step - THICKNESS * 4 - 1, step - THICKNESS * 4 - 1);
    }

    ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    if ( robotRealLocation != null ) {
//      int ss = (int)Math.min(stepX * 0.8f, stepY * 0.8f);
      int ss = (int)(step * 0.2f);
      g.setColor(new Color(1.0f, 0.0f, 0.0f, 0.6f));
//      g.fillOval(robotRealLocation.x + (stepX - ss) / 2, robotRealLocation.y + (stepY - ss) / 2, ss, ss);
      g.fillOval(robotRealLocation.x + ss, robotRealLocation.y + ss, step - ss * 2, step - ss * 2);
    }

    if ( solutionPoints != null && solutionPoints.size() > 1 ) {
      g.setColor(Color.red);
      ((Graphics2D)g).setStroke(gridStroke);
      for ( int i = 0; i < solutionPoints.size() - 1; i++ ) {
        Point aLoc = solutionPoints.get(i);
        Point nLoc = solutionPoints.get(i + 1);
//        g.drawLine(aLoc.x * stepX + stepX / 2, aLoc.y * stepY + stepY / 2, nLoc.x * stepX + stepX / 2, nLoc.y * stepY + stepY / 2);
        g.drawLine(aLoc.x * step + step / 2, aLoc.y * step + step / 2, nLoc.x * step + step / 2, nLoc.y * step + step / 2);
      }
    }
    if ( topLeft != null && bottomRight != null ) {
      Point tlLoc = topLeft.getLocation();
      Point brLoc = bottomRight.getLocation();
      Point sLoc = new Point(Math.min(tlLoc.x, brLoc.x), Math.min(tlLoc.y, brLoc.y));
      Point eLoc = new Point(Math.max(tlLoc.x, brLoc.x), Math.max(tlLoc.y, brLoc.y));
//      int dx = (Math.abs(brLoc.x - tlLoc.x) + 1) * stepX;
      int dx = (Math.abs(brLoc.x - tlLoc.x) + 1) * step;
//      int dy = (Math.abs(brLoc.y - tlLoc.y) + 1) * stepY;
      int dy = (Math.abs(brLoc.y - tlLoc.y) + 1) * step;
//System.out.println(sLoc.x + "," + sLoc.y + " " + dx + "x" + dy);
      g.setColor(cSelection);
//      g.fillRect(sLoc.x * stepX, sLoc.y * stepY, dx, dy);
      g.fillRect(sLoc.x * step, sLoc.y * step, dx, dy);
    }
  }

  public void setTopLeft(Tile tl)
  {
    topLeft = tl;
  }

  public void setBottomRight(Tile br)
  {
    bottomRight = br;
  }

  public void setLastJoint(Point gp)
  {
    lastJoint = gp;
  }

  public void setCurrentJoint(Point gp)
  {
    currentJoint = gp;
  }
/*
  public int getStepX()
  {
    return 0;
  }

  public int getStepY()
  {
    return 0;
  }

  public int getStep()
  {
    return 0;
  }
*/
//  public void adjustRobot()
//  {
//  }

  public void resetRobot()
  {
    robotStartMoving = -1;
    robotRealLocation = null;
  }

  public void setRobotRealLocation(Point loc)
  {
  }

  public void updateRobotLocation()
  {
    /*
    if ( solutionPoints != null && solutionPoints.size() > 1 ) {
      if ( robotNodeLocation == null ) {
        robotNodeLocation = solutionPoints.get(0);
      } else {
        if ( solutionPoints.contains(robotNodeLocation) ) {
        } else {
        }
    } else {
      robotNodeLocation = robotRealLocation = null;
    }
    */

    if ( robotStartMoving != -1 ) {
      long diff = System.currentTimeMillis() - robotStartMoving;
      if ( solutionPoints != null && solutionPoints.size() > 1 ) {
        int stepInterval = Math.max((int)(1000.0f / solutionPoints.size()), MIN_INTERVAL_PER_TILE);
        int robotStep = (int)(diff / stepInterval) % solutionPoints.size();
        Point nStep, pStep = solutionPoints.get(robotStep);
        if ( robotStep == solutionPoints.size() - 1 ) {
          nStep = solutionPoints.get(0);
        } else {
          nStep = solutionPoints.get(robotStep + 1);
        }
        robotNodeLocation = pStep;
        float deltaStep = (diff % stepInterval) / (float)stepInterval;
        int stepX = getWidth() / mcol;
        int stepY = getHeight() / mrow;
        int step = Math.min(stepX, stepY);
        robotRealLocation = new Point((int)(step * (pStep.x + (nStep.x - pStep.x) * deltaStep)), (int)(step * (pStep.y + (nStep.y - pStep.y) * deltaStep)));
      } else {
        robotStartMoving = -1;
        robotRealLocation = null;
      }
    } else {
      robotRealLocation = null;
    }
  }
/*
  public void updateRobotLocation()
  {
    if ( robotStartMoving != -1 ) {
      long diff = System.currentTimeMillis() - robotStartMoving;
      if ( solutionPoints != null && solutionPoints.size() > 1 ) {
        int stepInterval = Math.max((int)(1000.0f / solutionPoints.size()), MIN_INTERVAL_PER_TILE);
        int robotStep = (int)(diff / stepInterval) % solutionPoints.size();
        Point nStep, pStep = solutionPoints.get(robotStep);
        if ( robotStep == solutionPoints.size() - 1 ) {
          nStep = solutionPoints.get(0);
        } else {
          nStep = solutionPoints.get(robotStep + 1);
        }
        robotNodeLocation = pStep;
        float deltaStep = (diff % stepInterval) / (float)stepInterval;
        int stepX = getWidth() / mcol;
        int stepY = getHeight() / mrow;
        int step = Math.min(stepX, stepY);
//        robotRealLocation = new Point((int)(stepX * (pStep.x + (nStep.x - pStep.x) * deltaStep)), (int)(stepY * (pStep.y + (nStep.y - pStep.y) * deltaStep)));
        robotRealLocation = new Point((int)(step * (pStep.x + (nStep.x - pStep.x) * deltaStep)), (int)(step * (pStep.y + (nStep.y - pStep.y) * deltaStep)));
      } else {
        robotStartMoving = -1;
        robotRealLocation = null;
      }
    } else {
      robotRealLocation = null;
    }
  }
*/
}
